﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;

namespace DDD.Core {
    /// <summary>
    /// 抽象化2次元画像クラス
    /// </summary>
    public abstract class Image2D : Object {
        /// <summary>
        /// 画像の幅(ピクセル数)
        /// </summary>
        public abstract int Width { get; }

        /// <summary>
        /// 画像の高さ(ピクセル数)
        /// </summary>
        public abstract int Height { get; }

        /// <summary>
        /// 1ピクセルあたりのコンポーネント数
        /// </summary>
        public abstract int Cpp { get; }

        /// <summary>
        /// 画像のピクセルフォーマット
        /// </summary>
        /// <remarks>
        /// この画像の画像形式です。R8, RGB8など
        /// </remarks>
        public abstract PixelFormat PixelFormat { get; }

        /// <summary>
        /// ピクセルタイプ
        /// </summary>
        /// <remarks>
        /// CPU側で保存されるときのデータ型です。
        /// (メモ) 明らかに名前が微妙｡PixelComponentTypeあたりが妥当なように思える｡
        /// </remarks>
        public abstract PixelType PixelType { get; }

        /// <summary>
        /// PNG画像として保存します
        /// </summary>
        /// <remarks>
        /// このメソッドは主にデバッグ用途を想定しています。
        /// 現状Byte型の画像のみPNGファイルに保存可能です。Float型のものはエラーになります。
        /// 将来的には何とかしたいと考えています。
        /// </remarks>
        /// <param name="name">ファイル名</param>
        public abstract void Save (string name);
    }

    /// <summary>
    /// 2次元画像クラス
    /// </summary>
    public class Image2D<T> : Image2D where T : struct {
        #region Field
        int width;
        int height;
        int cpp;
        PixelFormat format;
        PixelType type;
        T[] pixels;
        T[] clearColor;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 画像は左上が(0,0)です。
        /// ピクセルは規定のクリア色(<see cref="ClearColor"/>)で塗りつぶされます。
        /// <list>
        ///   <item><see cref="DDD.Core.PixelFormat.Depth"/> = 1.0f</item>
        ///   <item>それ以外の画像形式 = 0.0f(全てのコンポーネントで)</item>
        /// </list>
        /// </remarks>
        /// <param name="format">画像フォーマット</param>
        /// <param name="width">画像の幅(ピクセル数)</param>
        /// <param name="height">画像の高さ(ピクセル数)</param>
        public Image2D (PixelFormat format, int width, int height) {
            if (width <= 0) {
                throw new ArgumentException ("Width is invalid");
            }
            if (height <= 0) {
                throw new ArgumentException ("Height is invalid");
            }
            var typeName = typeof (T).Name;
            if (!(typeName == "Byte" || typeName == "SByte" || typeName == "Int16" || typeName == "UInt16" || typeName == "Int32" || typeName == "UInt32" || typeName == "Single")) {
                throw new ArgumentException ("Type is invalid");
            }
            if (typeName == "Byte" && !(format == PixelFormat.R8ui || format == PixelFormat.RG8ui || format == PixelFormat.RGB8ui || format == PixelFormat.RGBA8ui)) {
                throw new ArgumentException ("Image<byte> is invalid for this format=" + format);
            }
            if (typeName == "SByte" && !(format == PixelFormat.R8i || format == PixelFormat.RG8i || format == PixelFormat.RGB8i || format == PixelFormat.RGBA8i)) {
                throw new ArgumentException ("Image<sbyte> is invalid for this format=" + format);
            }
            if (typeName == "Int16" && !(format == PixelFormat.R16i || format == PixelFormat.RG16i || format == PixelFormat.RGB16i || format == PixelFormat.RGBA16i)) {
                throw new ArgumentException ("Image<short> is invalid for this format=" + format);
            }
            if (typeName == "UInt16" && !(format == PixelFormat.R16ui || format == PixelFormat.RG16ui || format == PixelFormat.RGB16ui || format == PixelFormat.RGBA16ui ||
                                          format == PixelFormat.RGB10A2ui)) {
                throw new ArgumentException ("Image<ushort> is invalid for this format=" + format);
            }
            if (typeName == "Int32" && !(format == PixelFormat.R32i || format == PixelFormat.RG32i || format == PixelFormat.RGB32i || format == PixelFormat.RGBA32i)) {
                throw new ArgumentException ("Image<int> is invalid for this format=" + format);
            }
            if (typeName == "UInt32" && !(format == PixelFormat.R32ui || format == PixelFormat.RG32ui || format == PixelFormat.RGB32ui || format == PixelFormat.RGBA32ui)) {
                throw new ArgumentException ("Image<uint> is invalid for this format=" + format);
            }
            if (typeName == "Single" && !(format == PixelFormat.R8 || format == PixelFormat.R16 || format == PixelFormat.R16f || format == PixelFormat.R32f ||
                                          format == PixelFormat.RG8 || format == PixelFormat.RG16 || format == PixelFormat.RG16f || format == PixelFormat.RG32f ||
                                          format == PixelFormat.RGB8 || format == PixelFormat.RGB16 || format == PixelFormat.RGB16f || format == PixelFormat.RGB32f ||
                                          format == PixelFormat.RGBA8 || format == PixelFormat.RGBA16 || format == PixelFormat.RGBA16f || format == PixelFormat.RGBA32f ||
                                          format == PixelFormat.RGB9e5 || format == PixelFormat.R11G11B10f || format == PixelFormat.RGB10A2 ||
                                          format == PixelFormat.Depth)) {
                throw new ArgumentException ("Image<float> is invalid for this format=" + format);
            }

            this.format = format;
            this.type = PixelTypeExtensions.GetType<T> ();
            this.width = width;
            this.height = height;
            this.cpp = format.ToCpp ();
            this.pixels = new T[width * height * cpp];
            this.clearColor = new T[cpp];
            // 画像背景を強制的に白
            /*
            for(var i=0; i < cpp; i++) {
                switch (type) {
                    case PixelType.Byte: this.clearColor[i] = (T)(ValueType)(byte)1; break;
                    case PixelType.SByte: this.clearColor[i] = (T)(ValueType)(sbyte)1; break;
                    case PixelType.Short: this.clearColor[i] = (T)(ValueType)(short)1; break;
                    case PixelType.UShort: this.clearColor[i] = (T)(ValueType)(ushort)1; break;
                    case PixelType.Int: this.clearColor[i] = (T)(ValueType)(int)1; break;
                    case PixelType.UInt: this.clearColor[i] = (T)(ValueType)(uint)1; break;
                    case PixelType.Float: this.clearColor[i] = (T)(ValueType)(float)1; break;
                    default: new NotImplementedException ("Sorry"); break;
                }
            }
            * */
            if (format == PixelFormat.Depth) {
                this.clearColor[0] = (T)(ValueType)1.0f;
            }
            Clear ();
        }
        #endregion


        #region Property

        /// <summary>
        /// 画像の幅(ピクセル数)
        /// </summary>
        public override int Width {
            get { return width; }
        }

        /// <summary>
        /// 画像の高さ(ピクセル数)
        /// </summary>
        public override int Height {
            get { return height; }
        }


        /// <summary>
        /// 1ピクセルあたりのコンポーネント数
        /// </summary>
        public override int Cpp {
            get { return cpp; }
        }

        /// <summary>
        /// 画像のピクセルフォーマット
        /// </summary>
        /// <remarks>
        /// この画像の画像形式です。R8, RGB8など
        /// </remarks>
        public override PixelFormat PixelFormat {
            get { return format; }
        }

        /// <summary>
        /// ピクセルタイプ
        /// </summary>
        /// <remarks>
        /// CPU側で保存されるときのデータ型です。
        /// (メモ) 明らかに名前が微妙｡PixelComponentTypeあたりが妥当なように思える｡
        /// </remarks>
        public override PixelType PixelType {
            get {
                return type;
            }
        }

        /// <summary>
        /// 画像のクリア色
        /// </summary>
        public T[] ClearColor {
            get { return clearColor; }
        }


        /// <summary>
        /// 基本画像の全ピクセルを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// ピクセルは左上(0,0)から始めて行優先で列挙します。
        /// 行と行の間は隙間無くパックされています。
        /// 1ピクセルはR,G,B,Aの順番です。
        /// </remarks>
        public IEnumerable<T> Pixels {
            get { return pixels; }
        }
        #endregion

        #region Method
        /// <summary>
        /// 指定の領域の画像データを取得します
        /// </summary>
        /// <remarks>
        /// 画像データは左上を(0,0)先頭として行優先で隙間無く書き込まれます。コンポーネントの並びはRが先頭でR,G,B,Aの順番で並びます。
        /// 画像の外側にアクセスすると例外を発生します。
        /// </remarks>
        /// <param name="x">領域の開始位置のX座標(ピクセル)</param>
        /// <param name="y">領域の開始位置のY座標(ピクセル)</param>
        /// <param name="width">領域の幅(ピクセル)</param>
        /// <param name="height">領域の高さ(ピクセル)</param>
        /// <param name="pixels">結果を書き込む領域</param>
        public void Get (int x, int y, int width, int height, T[] pixels) {
            if (x < 0 || x >= Width || y < 0 || y >= Height) {
                throw new ArgumentException ("(x,y) is invalid");
            }
            if (width < 0 || height < 0) {
                throw new ArgumentException ("(width,height) is invalid");
            }
            if (x + width > Width || y + height > Height) {
                throw new ArgumentException ("(x+width,y+height) is invalid");
            }
            if (pixels == null) {
                throw new ArgumentNullException ("Pixels is null");
            }
            if (pixels.Length < width * height * Cpp) {
                throw new ArgumentException ("Pixels is too short");
            }

            for (var h = 0; h < height; h++) {
                Array.Copy (this.pixels, (y + h) * Width * Cpp, pixels, h * width * Cpp, width * Cpp);
            }
        }


        /// <summary>
        /// 指定の領域の画像データを変更します
        /// </summary>
        /// <remarks>
        /// 画像データは左上を(0,0)としてRが先頭でR,G,B,Aの順番で行優先で隙間無く並べます。
        /// 画像の外側にアクセスすると例外を発生します。
        /// </remarks>
        /// <param name="x">領域の開始位置のX座標(ピクセル)</param>
        /// <param name="y">領域の開始位置のY座標(ピクセル)</param>
        /// <param name="width">領域の幅(ピクセル)</param>
        /// <param name="height">領域の高さ(ピクセル)</param>
        /// <param name="pixels">画像データ</param>
        public void Set (int x, int y, int width, int height, T[] pixels) {
            if (x < 0 || x >= Width || y < 0 || y >= Height) {
                throw new ArgumentException ("(x,y) is invalid");
            }
            if (width < 0 || height < 0) {
                throw new ArgumentException ("(width,height) is invalid");
            }
            if (x + width > Width || y + height > Height) {
                throw new ArgumentException ("(x+width,y+height) is invalid");
            }
            if (pixels == null) {
                throw new ArgumentNullException ("Pixels is null");
            }
            if (pixels.Length < width * height * Cpp) {
                throw new ArgumentException ("Pixels is too short");
            }

            for (var h = 0; h < height; h++) {
                Array.Copy (pixels, h * width * Cpp, this.pixels, (y + h) * Width * Cpp, width * Cpp);
            }
        }

        /// <summary>
        /// クリア色を設定する
        /// </summary>
        /// <param name="clearColor">クリア色</param>
        public void SetClearColor (T[] clearColor) {
            if (clearColor == null) {
                throw new ArgumentNullException ("ClearColor is null");
            }
            if (clearColor.Length < cpp) {
                throw new ArgumentException ("ClearColor is too short");
            }

            Array.Copy (clearColor, this.clearColor, cpp);
        }

        /// <summary>
        /// 画像のクリア
        /// </summary>
        /// <remarks>
        /// 画像の全ピクセルをクリア色(<see cref="ClearColor"/>)で塗りつぶします｡
        /// </remarks>
        public void Clear () {
            for (var i = 0; i < pixels.Length; i++) {
                this.pixels[i] = clearColor[i % cpp];
            }
        }


        /// <summary>
        /// PNG画像として保存します
        /// </summary>
        /// <remarks>
        /// このメソッドは主にデバッグ用途を想定しています。
        /// 現状Byte型の画像のみPNGファイルに保存可能です。Float型のものはエラーになります。
        /// 将来的には何とかしたいと考えています。
        /// </remarks>
        /// <param name="name">ファイル名</param>
        public override void Save (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }

            ImageWriter.Save (name, this);
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                return base.References;
            }
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("{0}x{1}, {2}", Width, Height, PixelFormat);
        }
        #endregion


    }
}